/**
 * libp2p Remote 示例
 * 
 * 这个脚本演示如何在 protoactor-ts 中使用 libp2p 协议进行 Remote 通信
 * 启动多个节点，并使用 libp2p 协议在节点间传递消息
 */

import { Actor } from '../src/actor/actor';
import { PID } from '../src/actor/pid';
import { ActorSystem } from '../src/actor/actorSystem';
import { Props } from '../src/actor/props';
import { Context } from '../src/actor/context';
import { Remote, RemoteProtocolType } from '../src/remote/remote';
import { Cluster } from '../src/cluster/cluster';
import { ClusterConfig } from '../src/cluster/clusterConfig';
import { Libp2pProvider } from '../src/cluster/providers/libp2pProvider';
import { LocalIdentityLookup } from '../src/cluster/providers/localIdentityLookup';
import { ProcessRegistry } from '../src/actor/processRegistry';

// 延时函数
const delay = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));

// ====== 定义测试Actor ======

// 测试Actor消息类型
type TestActorMessage = 
  | { type: 'ping' }
  | { type: 'get-state' }
  | { type: 'update-state', value: string };

// 测试Actor
class TestActor implements Actor {
  private state: string = '';
  private receivedPings: number = 0;
  
  constructor(private id: string) {
    console.log(`[TestActor:${this.id}] 创建`);
  }
  
  async receive(context: Context): Promise<void> {
    try {
      // 获取消息
      const message = context.message();
      
      // 输出消息详情用于调试
      console.log(`[TestActor:${this.id}] 收到消息:`, message);
      console.log(`[TestActor:${this.id}] 消息发送者:`, context.sender());
      
      if (typeof message === 'object' && message !== null) {
        // 处理不同类型的消息
        if (message.type === 'ping') {
          console.log(`[TestActor:${this.id}] 收到ping请求`);
          this.receivedPings++;
          
          // 创建响应
          const response = {
            success: true,
            message: 'pong',
            source: this.id,
            receivedPings: this.receivedPings
          };
          
          console.log(`[TestActor:${this.id}] 发送ping响应:`, response);
          
          // 如果有发送者，发送响应
          const sender = context.sender();
          if (sender) {
            try {
              // 使用respond方法
              context.respond(response);
              console.log(`[TestActor:${this.id}] ping响应已发送`);
            } catch (error) {
              console.error(`[TestActor:${this.id}] 发送响应失败:`, error);
            }
          } else {
            console.warn(`[TestActor:${this.id}] 没有发送者，无法回复ping`);
          }
        } else if (message.type === 'get-state') {
          console.log(`[TestActor:${this.id}] 收到获取状态请求`);
          
          // 创建响应
          const response = {
            success: true,
            state: this.state || '未设置',
            receivedPings: this.receivedPings
          };
          
          console.log(`[TestActor:${this.id}] 发送状态响应:`, response);
          
          // 如果有发送者，发送响应
          const sender = context.sender();
          if (sender) {
            try {
              context.respond(response);
              console.log(`[TestActor:${this.id}] 状态响应已发送`);
            } catch (error) {
              console.error(`[TestActor:${this.id}] 发送响应失败:`, error);
            }
          } else {
            console.warn(`[TestActor:${this.id}] 没有发送者，无法回复状态请求`);
          }
        } else if (message.type === 'update-state' && 'value' in message) {
          console.log(`[TestActor:${this.id}] 收到更新状态请求:`, message.value);
          
          // 更新状态
          this.state = message.value;
          
          // 创建响应
          const response = {
            success: true,
            state: this.state,
            updated: true
          };
          
          console.log(`[TestActor:${this.id}] 发送更新状态响应:`, response);
          
          // 如果有发送者，发送响应
          const sender = context.sender();
          if (sender) {
            try {
              context.respond(response);
              console.log(`[TestActor:${this.id}] 更新状态响应已发送`);
            } catch (error) {
              console.error(`[TestActor:${this.id}] 发送响应失败:`, error);
            }
          } else {
            console.warn(`[TestActor:${this.id}] 没有发送者，无法回复更新状态请求`);
          }
        } else {
          console.warn(`[TestActor:${this.id}] 收到未知类型的消息:`, message);
          
          // 发送错误响应
          const errorResponse = {
            success: false,
            error: `未知的消息类型: ${JSON.stringify(message)}`
          };
          
          const sender = context.sender();
          if (sender) {
            try {
              context.respond(errorResponse);
            } catch (respondError) {
              console.error(`[TestActor:${this.id}] 发送错误响应失败:`, respondError);
            }
          }
        }
      } else {
        console.warn(`[TestActor:${this.id}] 收到非对象消息:`, message);
        
        // 发送错误响应
        const errorResponse = {
          success: false,
          error: `消息不是对象: ${message}`
        };
        
        const sender = context.sender();
        if (sender) {
          try {
            context.respond(errorResponse);
          } catch (respondError) {
            console.error(`[TestActor:${this.id}] 发送错误响应失败:`, respondError);
          }
        }
      }
    } catch (error) {
      console.error(`[TestActor:${this.id}] 处理消息时出错:`, error);
      
      // 发送错误响应
      const errorResponse = {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
      
      const sender = context.sender();
      if (sender) {
        try {
          context.respond(errorResponse);
        } catch (respondError) {
          console.error(`[TestActor:${this.id}] 发送错误响应失败:`, respondError);
        }
      }
    }
  }
}

// ====== 节点类 ======

class RemoteNode {
  private name: string;
  private port: number;
  private system!: ActorSystem;
  private remote!: Remote;
  private libp2pProvider!: Libp2pProvider;
  
  constructor(name: string, port: number, private bootstrapAddrs: string[] = []) {
    this.name = name;
    this.port = port;
  }
  
  /**
   * 启动远程系统
   * @param hostname 主机名
   * @param port 端口
   */
  async start(): Promise<void> {
    console.log(`启动节点: ${this.name}`);
    
    try {
      // 初始化 libp2p 节点
      console.log('正在初始化 libp2p 节点...');
      this.libp2pProvider = new Libp2pProvider({
        listenAddrs: [`/ip4/0.0.0.0/tcp/${this.port}`],
        bootstrapAddrs: this.bootstrapAddrs,
        enableMDNS: true,
        enableDHT: true,
        enableGossip: true
      });
      
      // 初始化 libp2p 节点
      await this.initLibp2pProvider();
      
      // 启动 libp2p 提供者
      await this.libp2pProvider.start();
      
      console.log('libp2p 节点已创建，PeerId:', this.libp2pProvider.getPeerId());
      console.log('使用自定义事件进行集群通信');
      
      // 初始化 ActorSystem
      this.system = new ActorSystem();
      
      // 初始化 ProcessRegistry
      ProcessRegistry.initialize(this.system);
      
      // 获取 peerId
      const peerId = this.libp2pProvider.getPeerId();
      if (!peerId) {
        throw new Error('Failed to get peerId from libp2p provider');
      }
      
      console.log(`节点 ${this.name} 的 peerId: ${peerId}`);
      
      // 设置本地地址为 libp2p:peerId 格式
      const localAddress = `libp2p:${peerId}`;
      console.log('设置节点地址为:', localAddress);
      ProcessRegistry.getInstance().address = () => localAddress;
      
      // 订阅系统事件
      this.system.eventStream.subscribe('DeadLetter', (msg: any) => {
        if (msg.type === 'DeadLetter') {
          console.log('DeadLetter:', msg);
        }
      });
      
      // 初始化 Remote
      this.remote = new Remote(this.system);
      this.remote
        .withProtocol(RemoteProtocolType.Libp2p)
        .withLibp2pProvider(this.libp2pProvider);
      
      // 注册 TestActor
      const producer = () => new TestActor(this.name);
      this.remote.register('TestActor', Props.fromProducer(producer));
      
      // 启动 Remote，使用本地地址作为hostname，port作为端口
      const [_, hostname] = localAddress.split(':');
      await this.remote.start(hostname, this.port);
      console.log('libp2p节点已启动');
      
    } catch (error) {
      console.error(`启动节点 ${this.name} 失败:`, error);
      throw error;
    }
  }
  
  /**
   * 初始化 libp2p 提供者
   * @private
   */
  private async initLibp2pProvider(): Promise<void> {
    // 创建一个简单的集群模拟对象，仅用于初始化 libp2p 提供者
    const clusterMock = {
      getActorSystem: () => this.system || ({ root: null } as any),
      getNodeId: () => this.name,
      getConfig: () => ({
        nodeAddress: `libp2p:${this.libp2pProvider.getPeerId() || 'unknown'}`,
        clusterName: "remote-libp2p-example",
        providerType: "libp2p",
        providerConfig: {},
        reconnectInterval: 5000,
        maxRetries: 3,
        retryBackoffFactor: 1.5
      }),
      router: {}
    };
    
    // 初始化 libp2p 提供者
    await this.libp2pProvider.init(clusterMock as any);
    
    // 确保 node 和 pubsub 已经初始化
    if (this.libp2pProvider['node'] && !this.libp2pProvider['node'].pubsub) {
      console.log('手动添加 pubsub 支持...');
      const { gossipsub } = await import('@chainsafe/libp2p-gossipsub');
      this.libp2pProvider['node'].pubsub = gossipsub({ allowPublishToZeroTopicPeers: true });
    }
  }
  
  createTestActor(name: string): PID {
    const producer = () => new TestActor(`${this.name}-${name}`);
    const props = Props.fromProducer(producer);
    return this.system.root.spawn(props);
  }
  
  getSystem(): ActorSystem {
    return this.system;
  }
  
  getAddress(): string {
    return `localhost:${this.port}`;
  }
  
  async shutdown(): Promise<void> {
    try {
      console.log(`关闭节点: ${this.name}`);
      
      // 关闭 libp2p 提供者
      await this.libp2pProvider.shutdown();
      
      // 关闭 Remote
      await this.remote.shutdown();
      
      console.log(`节点 ${this.name} 已关闭`);
    } catch (error) {
      console.error(`关闭节点 ${this.name} 失败:`, error);
    }
  }
}

// ====== 主函数 ======

async function main() {
  console.log('启动 libp2p Remote 示例');
  
  try {
    // 创建3个节点
    const node1 = new RemoteNode('node1', 12001);
    const node2 = new RemoteNode('node2', 12002);
    const node3 = new RemoteNode('node3', 12003);
    
    // 启动节点
    console.log('启动节点1...');
    await node1.start();
    
    console.log('启动节点2...');
    await node2.start();
    
    console.log('启动节点3...');
    await node3.start();
    
    console.log('所有节点启动完成');
    
    // 在每个节点上创建 Actor
    console.log('在每个节点上创建 Actor');
    const actor1 = node1.createTestActor('test1');
    const actor2 = node2.createTestActor('test2');
    const actor3 = node3.createTestActor('test3');
    
    console.log(`Actor1: ${actor1.toString()}`);
    console.log(`Actor2: ${actor2.toString()}`);
    console.log(`Actor3: ${actor3.toString()}`);
    
    // 获取各节点的地址
    const node1Address = node1.getAddress();
    const node2Address = node2.getAddress();
    const node3Address = node3.getAddress();
    
    console.log(`节点1地址: ${node1Address}`);
    console.log(`节点2地址: ${node2Address}`);
    console.log(`节点3地址: ${node3Address}`);
    
    // 等待网络连接建立
    console.log('等待网络连接建立...');
    await delay(3000);
    
    // 获取各节点的libp2p地址
    const node1PeerId = ProcessRegistry.getInstance().address();
    const node2PID = actor2.address;
    const node3PID = actor3.address;
    
    console.log(`节点1 PeerId: ${node1PeerId}`);
    console.log(`节点2 PID 地址: ${node2PID}`);
    console.log(`节点3 PID 地址: ${node3PID}`);
    
    // 从节点1向节点2上的Actor发送消息
    console.log('测试从节点1向节点2发送消息...');
    
    try {
      // 直接使用现有的actor2 PID
      console.log(`发送消息到 PID: ${actor2.toString()}`);
      
      // 发送 ping 消息并等待响应
      console.log('发送 ping 消息...');
      const response = await node1.getSystem().root.requestFuture(actor2, { type: 'ping' }, 10000).toPromise();
      console.log('收到响应:', response);
      
      if (response && typeof response === 'object' && 'success' in response) {
        console.log('Ping 成功!');
      } else {
        console.error('Ping 失败!');
      }
    } catch (error) {
      console.error('测试失败:', error);
    }
    
    // 等待一下
    await delay(1000);
    
    // 从节点1向节点3上的Actor发送消息
    console.log('测试从节点1向节点3发送消息...');
    
    try {
      // 直接使用现有的actor3 PID
      console.log(`发送消息到 PID: ${actor3.toString()}`);
      
      // 发送 ping 消息并等待响应
      console.log('发送 ping 消息...');
      const response = await node1.getSystem().root.requestFuture(actor3, { type: 'ping' }, 10000).toPromise();
      console.log('收到响应:', response);
      
      if (response && typeof response === 'object' && 'success' in response) {
        console.log('Ping 成功!');
      } else {
        console.error('Ping 失败!');
      }
    } catch (error) {
      console.error('测试失败:', error);
    }
    
    // 关闭所有节点
    console.log('测试完成，关闭所有节点...');
    await node1.shutdown();
    await node2.shutdown();
    await node3.shutdown();
    
    console.log('示例运行完成');
  } catch (error) {
    console.error('示例运行失败:', error);
  }
}

// 执行主函数
main().catch(error => {
  console.error('程序异常:', error);
  process.exit(1);
}); 